﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Borg
{
    [StructLayout(LayoutKind.Sequential)]
    public struct AssemblyAnalyzerResult
    {
        private readonly Uri originUri;
        private readonly Assembly assembly;

        private readonly IDictionary<SlotDefinitionAttribute, Type> slotDefinitions;
        private readonly IDictionary<SlotDefinitionAttribute, ISet<TagDefinitionAttribute>> tagDefinitions;
        private readonly IDictionary<SlotDefinitionAttribute, ISet<ParamDefinitionAttribute>> parameterDefinitions;

        private readonly IDictionary<ExtensionAttribute, Type> extensions;
        private readonly IDictionary<ExtensionAttribute, ISet<SlotAttribute>> slots;
        private readonly IDictionary<ExtensionAttribute, ISet<PlugAttribute>> plugs;
        private readonly IDictionary<ExtensionAttribute, ISet<ParamAttribute>> parameters;

        public AssemblyAnalyzerResult(Uri originUri, Assembly assembly)
        {
            this.originUri = originUri;
            this.assembly = assembly;

            slotDefinitions = new Dictionary<SlotDefinitionAttribute, Type>(new AttributeEqualityComparer());
            tagDefinitions = new Dictionary<SlotDefinitionAttribute, ISet<TagDefinitionAttribute>>(new AttributeEqualityComparer());
            parameterDefinitions = new Dictionary<SlotDefinitionAttribute, ISet<ParamDefinitionAttribute>>(new AttributeEqualityComparer());
            
            extensions = new Dictionary<ExtensionAttribute, Type>(new AttributeEqualityComparer());
            slots = new Dictionary<ExtensionAttribute, ISet<SlotAttribute>>(new AttributeEqualityComparer());
            plugs = new Dictionary<ExtensionAttribute, ISet<PlugAttribute>>(new AttributeEqualityComparer());
            parameters = new Dictionary<ExtensionAttribute, ISet<ParamAttribute>>(new AttributeEqualityComparer());
        }

        public Uri OriginUri
        {
            get { return originUri; }
        }

        public string Name
        {
            get { return assembly.GetName().Name; }
        }

        public Assembly Assembly
        {
            get { return assembly; }
        }

        public ISet<SlotDefinitionAttribute> GetSlotDefinitions()
        {
            return new HashSet<SlotDefinitionAttribute>(InternalGetSlotDefinitions());
        }

        public string GetSlotDefinitionName(SlotDefinitionAttribute slotDefinition)
        {
            return
                (string.IsNullOrEmpty(slotDefinition.Name)
                     ? GetSlotDefinitionType(slotDefinition).Name
                     : slotDefinition.Name);
        }

        public Type GetSlotDefinitionType(SlotDefinitionAttribute slotDefinition)
        {
            return slotDefinitions[slotDefinition];
        }

        public ISet<TagDefinitionAttribute> GetTagDefinitions(SlotDefinitionAttribute slotDefinition)
        {
            return new HashSet<TagDefinitionAttribute>(InternalGetTagDefinitions(slotDefinition));
        }

        public ISet<ParamDefinitionAttribute> GetParamDefinitions(SlotDefinitionAttribute slotDefinition)
        {
            return new HashSet<ParamDefinitionAttribute>(InternalGetParamDefinitions(slotDefinition));
        }

        internal ICollection<SlotDefinitionAttribute> InternalGetSlotDefinitions()
        {
            return slotDefinitions.Keys;
        }

        internal ISet<TagDefinitionAttribute> InternalGetTagDefinitions(SlotDefinitionAttribute slotDefinition)
        {
            return tagDefinitions[slotDefinition];
        }

        internal ISet<ParamDefinitionAttribute> InternalGetParamDefinitions(SlotDefinitionAttribute slotDefinition)
        {
            return parameterDefinitions[slotDefinition];
        }

        public ISet<ExtensionAttribute> GetExtensionTypes()
        {
            return new HashSet<ExtensionAttribute>(InternalGetExtensions());
        }

        public string GetExtensionTypeName(ExtensionAttribute extension)
        {
            return
                (string.IsNullOrEmpty(extension.Name)
                     ? GetExtensionTypeType(extension).Name
                     : extension.Name);
        }

        public Type GetExtensionTypeType(ExtensionAttribute extension)
        {
            return extensions[extension];
        }

        public ISet<SlotAttribute> GetSlotTypes(ExtensionAttribute extension)
        {
            return new HashSet<SlotAttribute>(InternalGetSlots(extension));
        }

        public ISet<PlugAttribute> GetPlugTypes(ExtensionAttribute extension)
        {
            return new HashSet<PlugAttribute>(InternalGetPlugs(extension));
        }

        public ISet<ParamAttribute> GetParameters(ExtensionAttribute extension)
        {
            return new HashSet<ParamAttribute>(InternalGetParameters(extension));
        }

        internal ICollection<ExtensionAttribute> InternalGetExtensions()
        {
            return extensions.Keys;
        }

        internal ISet<SlotAttribute> InternalGetSlots(ExtensionAttribute extension)
        {
            return slots[extension];
        }

        internal ISet<PlugAttribute> InternalGetPlugs(ExtensionAttribute extension)
        {
            return plugs[extension];
        }

        internal ISet<ParamAttribute> InternalGetParameters(ExtensionAttribute extension)
        {
            return parameters[extension];
        }

        internal void AddSlotDefinition(SlotDefinitionAttribute slotDefinition, Type type)
        {
            slotDefinitions.Add(slotDefinition, type);
            tagDefinitions.Add(slotDefinition, new HashSet<TagDefinitionAttribute>());
            parameterDefinitions.Add(slotDefinition, new HashSet<ParamDefinitionAttribute>());
        }

        internal void AddTagDefinition(SlotDefinitionAttribute slotDefinition, TagDefinitionAttribute tagDefinition)
        {
            tagDefinitions[slotDefinition].Add(tagDefinition);
        }

        internal void AddParamDefinition(SlotDefinitionAttribute slotDefinition, ParamDefinitionAttribute paramDefinition)
        {
            parameterDefinitions[slotDefinition].Add(paramDefinition);
        }

        internal void AddExtension(ExtensionAttribute extension, Type type)
        {
            extensions.Add(extension, type);
            slots.Add(extension, new HashSet<SlotAttribute>());
            plugs.Add(extension, new HashSet<PlugAttribute>());
            parameters.Add(extension, new HashSet<ParamAttribute>());
        }

        internal void AddSlot(ExtensionAttribute extension, SlotAttribute slot)
        {
            slots[extension].Add(slot);
        }

        internal void AddPlug(ExtensionAttribute extension, PlugAttribute plug)
        {
            plugs[extension].Add(plug);
        }

        internal void AddParam(ExtensionAttribute extension, ParamAttribute parameter)
        {
            parameters[extension].Add(parameter);
        }
        
        private sealed class AttributeEqualityComparer : IEqualityComparer<Attribute>
        {
            public bool Equals(Attribute x, Attribute y)
            {
                return (x == y);
            }

            public int GetHashCode(Attribute obj)
            {
                return obj.GetHashCode();
            }
        }
    }
}

